#ifndef GAMEENGINE_GRAPHICS_FRAMEBUFFER_OBJECT_H
#define GAMEENGINE_GRAPHICS_FRAMEBUFFER_OBJECT_H

#include <GameEngine/Graphics/graphics_settings.h>

#include <GameEngine/Graphics/graphic_resource.h>
#include <GameEngine/Graphics/Texture/texture2d.h>
#include <GameEngine/Graphics/renderbuffer_object.h>


#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

class Texture2d;

//=================================================================================


//! Wrapper for OpenGL framebuffer objects
/*!
 *  Framebuffer objects are an abstract interface that allows to render a scene to a texture rather
 *  than directly to the screen.
 *
 *  This has numerous applications in game programming - one simple example would be a CCTV camera
 *  that should capture images from its location dynamically and send them to a television screem,
 *  say, in a first person shooter game. One option could be to pre-render the images the camera
 *  should capture - still, this cannot account for the actions of the player in the range of
 *  detection of the camera, e.g., killing an ennemy and leaving its corpse there. To account for
 *  such interactions, one has to dynamically capture the scene from the camera point of view, and
 *  then render this scene on the quad that represents the TV screen in-game. One convenient way to
 *  do this is to capture/render the scene on a texture using a framebuffer object.
 *
 *  To capture a scene, client code can use the bind_buffer() and unbind_buffer() functions. All
 *  OpenGL primitives drawn between the bind_buffer() and unbind_buffer() functions will be redirected
 *  to the texture of the framebuffer object, rather than to the screen:
 *  @code
 *  FramebufferObject& fbo = ...;
 *  fbo.bind_buffer();
 *  // Draw stuff...
 *  fbo.unbind_buffer();
 *  @endcode
 *
 *  The framebuffer object shares the OpenGL context with the main window. Let's go back to the
 *  example of the CCTV camera: if we draw the scene directly after the bind_buffer() function,
 *  the position of the observer will be unchanged, i.e., the scene will be rendered as if it was
 *  seen from the position of the player (which is quite useless - if he has eyes, he does not
 *  need a camera from where he is!). Don't forget to change whatever needs to be changed when
 *  drawing to the framebuffer object. Oh, and it might also be a good idea to restore whatever
 *  you changed AFTER you have finished rendering to the framebuffer object.
 *
 *  Another point: it may be a good idea to clear the color and the depth of the framebuffer object
 *  before rendering to the framebuffer object, otherwise you will have a freezing-like effect in
 *  your framebuffer object (unless, of course, this is your goal, but I guess this rarely happens).
 *
 *  This framebuffer object class, though, does not offer the full set of functionalities exposed
 *  in the OpenGL engine. The most notable limitations are the inability to use multisampling, to
 *  render depth data to a texture, or to render to multiple textures. Some of these limitations
 *  may be released in future versions of GameEngine.
 *
 @note This class must always be manipulated AFTER a valid OpenGL context has been created. If
       no OpenGL context is available, any call to any function or constructor of this class
	   will result in an application crash.
 */
class _Graphics_decl FramebufferObject : public GraphicResource {
	GAMEENGINE_DECLARE_RTTI_PARENT(FramebufferObject,GraphicResource)
	//! The resource manager class is friend of the framebuffer object class because it must be able to access its ID and context
	friend class ResourceManager<ID,CONTEXTID>;

public:
	//! Possible status of a framebuffer object
	enum Status {
		//! Framebuffer is complete and ready to use
		UndefinedStatus = 0,
		//! Framebuffer is complete and ready to use
		CompleteStatus = GL_FRAMEBUFFER_COMPLETE_EXT,
		//! Framebuffer format is not supported by hardware
		UnsupportedStatus = GL_FRAMEBUFFER_UNSUPPORTED_EXT,
		//! Framebuffer is incomplete because some attachments are missing
		MissingAttachmentStatus = GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT,
		//! Framebuffer is incomplete because some attachments are duplicated
		IncompleteAttachmentStatus = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT,
		//! Framebuffer is incomplete because attachments do not have the same dimensions
		IncompleteDimensionStatus = GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT,
		//! Framebuffer is incomplete because attachments do not have the same formats
		IncompleteFormatStatus = GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT,
		//! Framebuffer is incomplete because no draw buffer is attached to it
		IncompleteDrawBufferStatus = GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT,
		//! Framebuffer is incomplete because no read buffer is attached to it
		IncompleteReadBufferStatus = GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT
	};

public:
	FramebufferObject();
	FramebufferObject(const FramebufferObject& rhs);
	virtual ~FramebufferObject();
	FramebufferObject& operator=(const FramebufferObject& rhs);

	bool initialized() const;

	bool bind_buffer() const;
	bool unbind_buffer() const;

	bool set_size(RenderbufferObject::RenderbufferSize width, RenderbufferObject::RenderbufferSize height);

	Texture2d& texture();
	const Texture2d& texture() const;

	Status get_status() const;

private:
	void clean_id();
	void create_id();

	virtual void destroy_resource() const;
	virtual CONTEXTID context() const;
	virtual ID reference() const;

private:
	ID id_;
	Texture2d texture_;
	RenderbufferObject render_buffer_;
};

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif

#endif
